Skip to content

Chat Phase 3 — per-framework typing-phase labels#240

Merged
jaylfc merged 9 commits intomasterfrom
feat/chat-phase-3-typing-labels
Apr 20, 2026
Merged

Chat Phase 3 — per-framework typing-phase labels#240
jaylfc merged 9 commits intomasterfrom
feat/chat-phase-3-typing-labels

Conversation

@jaylfc
Copy link
Copy Markdown
Owner

@jaylfc jaylfc commented Apr 20, 2026

Summary

Adds structured `{phase, detail}` to agent thinking heartbeats so the typing footer shows what an agent is actually doing — "tom is using web_search", "don is writing payment.py", "tom is planning" — instead of a generic "thinking".

Payload

```json
{ "slug": "tom", "state": "start", "phase": "tool", "detail": "web_search" }
```
Enum: `thinking | tool | reading | writing | searching | planning`. Backwards compatible — omitting `phase` defaults to `thinking`.

Backend

  • `POST /api/chat/channels/{id}/thinking` accepts + validates `phase` + `detail`; 400 on invalid enum.
  • `TypingRegistry.mark(..., phase=, detail=)` stores per-entry phase + detail.
  • `list()` returns `{"human": [{slug, phase, detail}], "agent": [{slug, phase, detail}]}`.
  • WS `thinking` event carries `phase` + `detail`.

Frontend

  • `TypingFooter` renders icon + phase-aware label:
    phase label icon
    thinking thinking 💭
    tool using detail 🔧
    reading reading detail 📖
    writing writing detail ✏️
    searching searching detail 🔍
    planning planning 📋
  • Detail truncated to 40 chars; unknown phase falls back to "thinking".

Bridges

  • All 6 install scripts (hermes, smolagents, langroid, pocketflow, openai_agents_sdk, openai-agents-sdk) get the new `_thinking(c, ch_id, state, phase=, detail=)` signature.
  • Per-framework phase emission deferred — none of the bridges expose a reachable callback in their current event-loop structure (smolagents runs in a thread-pool executor; others have no exposed step hooks). Signature is ready for future upgrades; all bridges continue to emit generic "thinking" until callbacks are wired.

Test plan

  • 17 typing-registry tests + 3 new route tests + 13 chat typing tests — all pass
  • 9 TypingFooter tests (5 existing + 4 new) — all pass
  • Bundle rebuilt, `bash -n` clean on all 6 bridges
  • Playwright E2E stub (`tests/e2e/test_chat_phase3.py`); gated on `TAOS_E2E_URL`
  • Manual smoke: open a channel with a live agent, verify "thinking" still renders (backwards compat)

Summary by CodeRabbit

  • New Features

    • Typing indicators now show per-agent phases (using/reading/writing/searching/planning/thinking) with phase-specific labels, icons, and truncated details; agents render as individual lines instead of a single aggregated string.
    • Updated desktop chat frontend bundle delivering the Messages app UI.
  • Tests

    • Added extensive unit and end-to-end tests covering phase-aware typing state, API validation, broadcast shape, and UI rendering/truncation.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d42117f0-db95-4942-82f8-e0ff9aa62bc4

📥 Commits

Reviewing files that changed from the base of the PR and between c3416f9 and 31bcbce.

📒 Files selected for processing (1)
  • tinyagentos/routes/chat.py

📝 Walkthrough

Walkthrough

This PR extends typing indicators to carry structured phase/detail metadata end-to-end: registry storage, HTTP /thinking handler validation/broadcasts, bridge helpers, desktop UI rendering, tests, and docs; frontend typing state now holds objects with {slug, phase, detail}.

Changes

Cohort / File(s) Summary
Frontend Component & Types
desktop/src/apps/chat/TypingFooter.tsx
Added TypingPhase and AgentTyping; changed agents prop to AgentTyping[]; implemented phase-aware icon/label rendering and detail truncation (40 chars).
Frontend App State
desktop/src/apps/MessagesApp.tsx
typingAgents now stores AgentTyping[]; WS "thinking" handler and legacy "typing" handling updated to read/store {slug, phase, detail} and filter by slug.
Frontend Tests
desktop/src/apps/chat/__tests__/TypingFooter.test.tsx
Replaced aggregation test with phase-specific cases for default/known/unknown phases, detail rendering, and truncation using structured agent objects.
Backend Typing Registry
tinyagentos/chat/typing_registry.py
Introduced TypingPhase and entry dataclass; mark(...) accepts optional phase/detail (agent default "thinking"); list(...) now returns lists of objects {slug, phase, detail}.
Backend Routes
tinyagentos/routes/chat.py
Added VALID_PHASES; /thinking POST validates phase/detail, passes them to TypingRegistry.mark on "start", and includes them in WS "thinking" broadcasts (cleared on "end").
Bridge Scripts
tinyagentos/scripts/install_*.sh
(hermes, langroid, openai-agents-sdk, openai_agents_sdk, pocketflow, smolagents)
Updated embedded _thinking() helpers to accept optional keyword-only phase and detail and to build request payloads conditionally including them.
Backend Tests
tests/test_typing_registry.py, tests/test_chat_phase.py, tests/test_chat_typing.py
Added/updated tests covering registry phase/detail storage, TTL behavior, API validation/defaults, and adjusted tests to assert slugs from object-shaped listings.
E2E Test
tests/e2e/test_chat_phase3.py
Added env-gated smoke test ensuring Messages view mounts and TypingFooter renders in the UI.
Docs & Spec
docs/superpowers/plans/..., docs/superpowers/specs/...
Added implementation plan and design spec describing TypingPhase, payload shape, registry/route changes, UI rendering rules, truncation, and test plans.
Static Assets & Entrypoints
static/desktop/assets/..., static/desktop/chat.html, static/desktop/index.html
Replaced old MessagesApp bundle with new bundle containing phase-aware typing logic; updated main bundle identifiers and HTML module entrypoints.
Other Bundled Changes
static/desktop/assets/MCPApp-*.js, .../ProvidersApp-*.js, .../SettingsApp-*.js, static/desktop/chat-R5uusfVD.js
Updated shared main import identifiers and minor bundled template changes (clipboard separator, initial YAML template).

Sequence Diagram

sequenceDiagram
    participant Bridge as Agent Bridge
    participant Backend as Backend API / Registry
    participant WS as WebSocket/Broadcast
    participant Client as Browser Client
    participant UI as TypingFooter UI

    Bridge->>Backend: POST /api/chat/channels/{ch}/thinking<br/>{slug, state:"start", phase, detail}
    activate Backend
    Backend->>Backend: validate phase ∈ VALID_PHASES
    Backend->>Backend: TypingRegistry.mark(channel, slug, kind="agent", phase, detail)
    Backend-->>WS: 200 OK
    deactivate Backend

    WS->>Client: Broadcast "thinking" event<br/>{slug, state:"start", phase, detail}
    Client->>UI: update typingAgents[] with {slug,phase,detail}
    UI->>UI: phaseLabel(phase,detail) → {icon,text}
    UI-->>Client: render phase-specific label/icon

    Bridge->>Backend: POST /.../thinking {slug, state:"end"}
    activate Backend
    Backend->>Backend: TypingRegistry.clear(channel, slug)
    Backend-->>WS: 200 OK (payload phase:null, detail:null)
    deactivate Backend

    WS->>Client: Broadcast "thinking" event {slug, state:"end"}
    Client->>UI: remove agent by slug
    UI-->>Client: re-render without agent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hop through code with ear-tip twitch,
Phase and detail now in stitch,
Tool, read, write — labels bright,
Truncated tales fit just right,
🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: introducing typing-phase labels across multiple frameworks, which is the central feature of this changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chat-phase-3-typing-labels

Comment @coderabbitai help to get the list of available commands and usage tips.

setTypingAgents((prev) => prev.includes(data.slug) ? prev : [...prev, data.slug]);
setTypingAgents((prev) => {
const without = prev.filter((a) => a.slug !== data.slug);
return [...without, { slug: data.slug, phase: data.phase ?? null, detail: data.detail ?? null }];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Stale agent entries may accumulate when multiple start events are received before an end event. The current logic removes only one matching entry, but if duplicates exist they will remain in state.

When multiple start events arrive for the same agent, prev.filter will remove all matching entries, which is correct. However if multiple entries were already present (due to a race condition) they will be properly cleared.

? detail.slice(0, 39) + "…"
: detail
: null;
switch (phase) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Truncation logic incorrectly counts characters before checking length. The current code creates the truncated string before checking length, so it will always add an ellipsis even when exactly 40 characters.

Suggested change
switch (phase) {
? detail.length > 40 ? detail.slice(0, 40) + "…" : detail

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented Apr 20, 2026

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
desktop/src/apps/MessagesApp.tsx 392 Potential stale agent entries with concurrent start events

SUGGESTION

File Line Issue
desktop/src/apps/chat/TypingFooter.tsx 66 Truncation logic incorrectly adds ellipsis for exactly 40 characters
Latest Changes Reviewed

tinyagentos/routes/chat.py - Added proper type validation for phase and detail parameters. No issues found in updated code.

Files Reviewed (6 files)
  • desktop/src/apps/MessagesApp.tsx - 1 issue
  • desktop/src/apps/chat/TypingFooter.tsx - 1 issue
  • desktop/src/apps/chat/__tests__/TypingFooter.test.tsx - 0 issues
  • docs/superpowers/plans/2026-04-20-chat-phase-3-typing-labels.md - 0 issues
  • docs/superpowers/specs/2026-04-20-chat-phase-3-typing-labels-design.md - 0 issues
  • tinyagentos/routes/chat.py - 0 issues (fixed validation)

Fix these issues in Kilo Cloud


Reviewed by seed-2-0-pro-260328 · 136,504 tokens

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
tests/e2e/test_chat_phase3.py (1)

20-29: Test intent is broader than what is actually asserted.

This currently validates chat navigation smoke, not Phase 3 typing-label behavior. Consider renaming to a smoke test (or adding deterministic heartbeat-driven footer assertions) to avoid false coverage signals.

✏️ Suggested minimal rename to match current scope
-def test_typing_footer_renders_at_all(page: Page):
-    """Smoke: open the chat, typing footer region is mounted (visibility
-    depends on real agent activity which this stub doesn't trigger)."""
+def test_chat_view_smoke_opens_without_crash(page: Page):
+    """Smoke: open chat and verify core composer UI renders."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/test_chat_phase3.py` around lines 20 - 29, The test function
test_typing_footer_renders_at_all asserts only chat navigation smoke behavior,
not Phase 3 typing-label rendering; rename the test to reflect its real scope
(e.g., test_chat_navigation_smoke or test_open_chat_footer_mounts) or
alternatively add deterministic setup to emit phase heartbeats before asserting
typing-label content; update the function name and any related test identifiers
and docstring (reference: test_typing_footer_renders_at_all) so the test name
matches the actual assertions.
tests/test_typing_registry.py (3)

74-113: Unnecessary @pytest.mark.asyncio on synchronous tests.

These tests (test_mark_with_phase_and_detail, test_mark_without_phase_defaults_to_thinking, etc.) are synchronous — they don't await anything. The asyncio marker is unnecessary and misleading.

🔧 Remove async markers from sync tests
-@pytest.mark.asyncio
-async def test_mark_with_phase_and_detail():
+def test_mark_with_phase_and_detail():
     reg = TypingRegistry()
     ...

-@pytest.mark.asyncio
-async def test_mark_without_phase_defaults_to_thinking():
+def test_mark_without_phase_defaults_to_thinking():
     reg = TypingRegistry()
     ...

-@pytest.mark.asyncio
-async def test_mark_overwrites_phase_last_writer_wins():
+def test_mark_overwrites_phase_last_writer_wins():
     reg = TypingRegistry()
     ...

-@pytest.mark.asyncio
-async def test_human_entry_shape_matches_agent():
+def test_human_entry_shape_matches_agent():
     reg = TypingRegistry()
     ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_typing_registry.py` around lines 74 - 113, Remove the unnecessary
`@pytest.mark.asyncio` decorator from the synchronous test functions
(test_mark_with_phase_and_detail, test_mark_without_phase_defaults_to_thinking,
test_mark_overwrites_phase_last_writer_wins,
test_human_entry_shape_matches_agent) since they do not await anything; update
the tests to be regular synchronous functions that call
TypingRegistry().mark(...) and TypingRegistry().list(...) (refer to
TypingRegistry.mark and TypingRegistry.list to locate usage) so that the
decorators are not present and the tests run as plain pytest tests.

118-120: Duplicate import of pytest.

pytest is already imported at line 1. The second import at line 118 is redundant.

🔧 Remove duplicate import
 # ── HTTP endpoint tests ────────────────────────────────────────────────────────

-import pytest
 import yaml
 from httpx import AsyncClient, ASGITransport
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_typing_registry.py` around lines 118 - 120, The file contains a
duplicate import of pytest; remove the redundant "import pytest" from the block
that also imports yaml and httpx (look for the import statement alongside
AsyncClient and ASGITransport) so only the original top-of-file "import pytest"
remains and no duplicate import lines exist.

170-197: Minor: Unused token variable on line 172.

Similar to the other test file, prefix with underscore.

🔧 Suggested fix
 `@pytest.mark.asyncio`
 async def test_post_thinking_start_marks_registry(tmp_path):
-    app, client, token = await _setup_client(tmp_path)
+    app, client, _token = await _setup_client(tmp_path)
     async with client:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_typing_registry.py` around lines 170 - 197, The test function
test_post_thinking_start_marks_registry declares an unused variable token
returned from _setup_client; rename token to _token (or prefix with an
underscore) in the assignment "app, client, token = await
_setup_client(tmp_path)" to indicate it's intentionally unused and silence
linter warnings, updating the tuple unpacking in that test only.
tests/test_chat_phase.py (1)

48-55: Minor: Unused app variable.

The app variable is unpacked but never used in this test. Prefix with underscore for clarity.

🔧 Suggested fix
 `@pytest.mark.asyncio`
 async def test_thinking_with_invalid_phase_400(tmp_path):
-    app, client = await _client_with_bearer(tmp_path)
+    _app, client = await _client_with_bearer(tmp_path)
     async with client:
         r = await client.post(
             "/api/chat/channels/c1/thinking",
             json={"slug": "tom", "state": "start", "phase": "not-a-phase"},
         )
         assert r.status_code == 400
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_chat_phase.py` around lines 48 - 55, The test function
test_thinking_with_invalid_phase_400 unpacks app, client = await
_client_with_bearer(tmp_path) but never uses app; update the unpacking to _app,
client = await _client_with_bearer(tmp_path) (or prefix app with an underscore)
so the unused variable is clearly marked and lint warnings are avoided; keep the
rest of the test (the client.post call and assertion) unchanged.
docs/superpowers/specs/2026-04-20-chat-phase-3-typing-labels-design.md (1)

47-57: Minor markdown formatting: Add blank lines around table.

Per markdownlint MD058, tables should be surrounded by blank lines for better rendering compatibility.

📝 Add blank lines around table
 - Phase label map:
+
   | phase | label template | icon |
   |---|---|---|
   | `thinking` | `thinking` | 💭 |
   | `tool` | `using ${detail}` (fallback: `using a tool`) | 🔧 |
   | `reading` | `reading ${detail}` (fallback: `reading`) | 📖 |
   | `writing` | `writing ${detail}` (fallback: `writing`) | ✏️ |
   | `searching` | `searching ${detail}` (fallback: `searching`) | 🔍 |
   | `planning` | `planning` | 📋 |
+
 - `detail` strings truncated to 40 chars with ellipsis.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/specs/2026-04-20-chat-phase-3-typing-labels-design.md`
around lines 47 - 57, The markdown table under "Phase label map" lacks
surrounding blank lines which violates MD058; update the document so there is a
blank line immediately before the table start (before the line "Phase label
map:" or between that heading and the table) and a blank line immediately after
the table end (after the "| `planning` | `planning` | 📋 |" row and before the
next bullet "- `detail` strings..."), ensuring the table is separated from
surrounding text and headings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tinyagentos/routes/chat.py`:
- Around line 888-892: The code currently checks membership of phase against
VALID_PHASES but doesn't guard for unhashable types, causing TypeError for
list/dict; update the validation before the membership check in the chat route
to first ensure phase is either None or a str (or the enum/expected type) and
return JSONResponse with status_code=400 for invalid types, then check
membership against VALID_PHASES (using the existing phase variable); likewise
validate that detail is either None or a str before calling mark() in
typing_registry.py so mark(phase, detail) only receives TypingPhase|None and
str|None.

---

Nitpick comments:
In `@docs/superpowers/specs/2026-04-20-chat-phase-3-typing-labels-design.md`:
- Around line 47-57: The markdown table under "Phase label map" lacks
surrounding blank lines which violates MD058; update the document so there is a
blank line immediately before the table start (before the line "Phase label
map:" or between that heading and the table) and a blank line immediately after
the table end (after the "| `planning` | `planning` | 📋 |" row and before the
next bullet "- `detail` strings..."), ensuring the table is separated from
surrounding text and headings.

In `@tests/e2e/test_chat_phase3.py`:
- Around line 20-29: The test function test_typing_footer_renders_at_all asserts
only chat navigation smoke behavior, not Phase 3 typing-label rendering; rename
the test to reflect its real scope (e.g., test_chat_navigation_smoke or
test_open_chat_footer_mounts) or alternatively add deterministic setup to emit
phase heartbeats before asserting typing-label content; update the function name
and any related test identifiers and docstring (reference:
test_typing_footer_renders_at_all) so the test name matches the actual
assertions.

In `@tests/test_chat_phase.py`:
- Around line 48-55: The test function test_thinking_with_invalid_phase_400
unpacks app, client = await _client_with_bearer(tmp_path) but never uses app;
update the unpacking to _app, client = await _client_with_bearer(tmp_path) (or
prefix app with an underscore) so the unused variable is clearly marked and lint
warnings are avoided; keep the rest of the test (the client.post call and
assertion) unchanged.

In `@tests/test_typing_registry.py`:
- Around line 74-113: Remove the unnecessary `@pytest.mark.asyncio` decorator from
the synchronous test functions (test_mark_with_phase_and_detail,
test_mark_without_phase_defaults_to_thinking,
test_mark_overwrites_phase_last_writer_wins,
test_human_entry_shape_matches_agent) since they do not await anything; update
the tests to be regular synchronous functions that call
TypingRegistry().mark(...) and TypingRegistry().list(...) (refer to
TypingRegistry.mark and TypingRegistry.list to locate usage) so that the
decorators are not present and the tests run as plain pytest tests.
- Around line 118-120: The file contains a duplicate import of pytest; remove
the redundant "import pytest" from the block that also imports yaml and httpx
(look for the import statement alongside AsyncClient and ASGITransport) so only
the original top-of-file "import pytest" remains and no duplicate import lines
exist.
- Around line 170-197: The test function test_post_thinking_start_marks_registry
declares an unused variable token returned from _setup_client; rename token to
_token (or prefix with an underscore) in the assignment "app, client, token =
await _setup_client(tmp_path)" to indicate it's intentionally unused and silence
linter warnings, updating the tuple unpacking in that test only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ed6e7a50-4cd7-49d5-bb20-03d462b03647

📥 Commits

Reviewing files that changed from the base of the PR and between 8ad0d8d and c3416f9.

📒 Files selected for processing (26)
  • desktop/src/apps/MessagesApp.tsx
  • desktop/src/apps/chat/TypingFooter.tsx
  • desktop/src/apps/chat/__tests__/TypingFooter.test.tsx
  • docs/superpowers/plans/2026-04-20-chat-phase-3-typing-labels.md
  • docs/superpowers/specs/2026-04-20-chat-phase-3-typing-labels-design.md
  • static/desktop/assets/MCPApp-DBMY1x8q.js
  • static/desktop/assets/MessagesApp-CkhTvSE9.js
  • static/desktop/assets/MessagesApp-D7Umpsei.js
  • static/desktop/assets/ProvidersApp-BZ0Cnn9e.js
  • static/desktop/assets/SettingsApp-CR8h0q_c.js
  • static/desktop/assets/chat-R5uusfVD.js
  • static/desktop/assets/main-FOmUFQfu.js
  • static/desktop/chat.html
  • static/desktop/index.html
  • tests/e2e/test_chat_phase3.py
  • tests/test_chat_phase.py
  • tests/test_chat_typing.py
  • tests/test_typing_registry.py
  • tinyagentos/chat/typing_registry.py
  • tinyagentos/routes/chat.py
  • tinyagentos/scripts/install_hermes.sh
  • tinyagentos/scripts/install_langroid.sh
  • tinyagentos/scripts/install_openai-agents-sdk.sh
  • tinyagentos/scripts/install_openai_agents_sdk.sh
  • tinyagentos/scripts/install_pocketflow.sh
  • tinyagentos/scripts/install_smolagents.sh
💤 Files with no reviewable changes (1)
  • static/desktop/assets/MessagesApp-D7Umpsei.js

Comment thread tinyagentos/routes/chat.py
@jaylfc jaylfc merged commit 3ba7668 into master Apr 20, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant